`include "mycpu.h"

module if_stage(
    input                          clk            ,
    input                          reset          ,
    input                          flush          ,
    //allwoin
    input                          ds_allowin     ,
    //brbus
    input  [`BR_BUS_WD       -1:0] br_bus         ,
    //to ds
    output                         fs_to_ds_valid ,
    output [`FS_TO_DS_BUS_WD -1:0] fs_to_ds_bus   ,
    //from ws
    input [`WS_TO_FS_BUS_WD -1:0] ws_to_fs_bus   ,
    //from ms
    input                      [31:0]ms_pc       ,
    //tlb
    input tlb_found,
    input tlb_valid,
    // inst sram interface
    output        inst_sram_req,
    output        inst_sram_wr,
    output [1:0]  inst_sram_size,
    output [3:0]  inst_sram_wstrb,
    output [31:0] inst_sram_addr,
    output [31:0] inst_sram_wdata,
    input        inst_sram_addr_ok,
    input        inst_sram_data_ok,
    input  [31:0] inst_sram_rdata
);
wire        pfs_ready_go;

reg         fs_valid;
wire        fs_ready_go;
wire        fs_allowin;
wire        to_fs_valid;
reg   [31:0]save_inst;//use to save inst for several cycle
reg         save;//use to indicate whether inst in save_inst is valid
reg         abandon;//whether abandon the next data we received
reg         bd_done;
reg  [`BR_BUS_WD-1:0]br_bus_r;      //to save br_bus
reg br_bus_r_valid;  //to indicate whether br_bus_r is valid
reg flush_r;  //to save flush
reg eret_r;     //to save eret
reg [31:0]epc_r;      //to save epc
wire fs_bd;
wire [31:0] seq_pc;
wire [31:0] nextpc;

wire         br_stall;
wire         br_taken;
wire [ 31:0] br_target;
wire         br_stall_r;
wire         br_taken_r;
wire [ 31:0] br_target_r;
wire         tlb_r;
reg          tlb_r_r;
wire         eret;
wire [ 31:0] epc;
wire refetch;
wire fs_refetch;
reg  fs_refetch_r;
wire ws_refetch;
reg  ws_refetch_r;
wire [31:0]ws_pc;
reg  [31:0]ws_pc_r;
reg  refetch_r;

assign {refetch,fs_bd,br_stall,br_taken,br_target} = br_bus;
assign {br_stall_r,br_taken_r,br_target_r} = br_bus_r;
assign {ws_refetch,ws_pc,tlb_r,eret,epc} = ws_to_fs_bus;
wire         fs_ex;
reg         fs_ex_r;
wire [31:0] fs_inst;
reg  [31:0] fs_pc;
wire        pfs_ex;

wire    [31:0] badvaddr;
wire        f_adel;
wire        f_tlb_r;
wire   [4:0]fs_excode;
assign fs_to_ds_bus = { fs_refetch|fs_refetch_r,//103:103
                        f_tlb_r,    //102:102
                        fs_excode,//101:97
                        badvaddr, //96:65
                        fs_ex,   //64:64
                       fs_inst ,
                       fs_pc   };


// exception
assign f_adel = ~(fs_pc[1:0] == 2'b00);
assign f_tlb_r = ~tlb_found;
assign fs_excode = f_adel?5'h04:5'h02;
assign pfs_ex = inst_sram_req&(~tlb_found|~tlb_valid)&~flush;
//assign fs_ex   =   (f_adel|(fs_refetch|fs_refetch_r)) & fs_valid & ~flush;
assign badvaddr = (fs_ex) ? fs_pc : 32'h0;
assign fs_refetch = refetch;
assign fs_ex=fs_ex_r|(f_adel|(fs_refetch|fs_refetch_r)) & fs_valid;
always @(posedge clk) begin
    if (reset) begin
        fs_ex_r <= 0;  
    end
    else if (to_fs_valid && fs_allowin) begin
        fs_ex_r <= pfs_ex;
    end
    
end



// pre-IF stage
assign pfs_ready_go = inst_sram_req & (inst_sram_addr_ok & ~br_stall |(~tlb_found|~tlb_valid));
assign to_fs_valid  = pfs_ready_go;
assign seq_pc       = fs_pc + 3'h4;
assign nextpc       =   ws_refetch?   ws_pc:
                        ws_refetch_r? ws_pc_r:
                        eret?      epc :
                        eret_r?    epc_r:
                        (flush|flush_r)?
                        ((tlb_r|tlb_r_r)?32'hbfc00200:32'hbfc00380):
                        br_taken & (fs_ready_go & ds_allowin|bd_done & ~br_stall)?
                        br_target:
                        (br_bus_r_valid & br_taken_r & ~br_stall_r&bd_done)
                        ? br_target_r : seq_pc; 

// IF stage
assign fs_ready_go    = (inst_sram_data_ok | save |(fs_ex)) & ~((flush_r|flush) & abandon);
assign fs_allowin     = !fs_valid || fs_ready_go && ds_allowin ;
assign fs_to_ds_valid =  fs_valid && fs_ready_go;

always @(posedge clk) begin
    if (reset|((flush|flush_r) & ~abandon& ~(pfs_ready_go & fs_allowin & ~abandon&(flush_r|flush)))) begin
        fs_valid <= 1'b0;
    end
    else if (fs_allowin) begin
        fs_valid <= to_fs_valid;
    end

    if (reset) begin
        fs_pc <= 32'hbfbffffc;  //trick: to make nextpc be 0xbfc00000 during reset 
    end
    else if (to_fs_valid && fs_allowin) begin
        fs_pc <= nextpc;
    end
    
end
always @(posedge clk) begin
    if(reset)begin
        save <= 1'b0;
    end
    else if((flush|flush_r)&abandon)begin
        save <= 1'b0;
    end
    else if(fs_ready_go & ~ds_allowin)begin
        save <= 1'b1;
    end
    else begin
        save <= 1'b0;
    end
end
always @(posedge clk) begin
    if(reset)begin
        save_inst <= 32'b0;
    end
    else if(fs_ready_go & ~ds_allowin)begin
        save_inst <= inst_sram_rdata;
    end
    else begin
        save_inst <= save_inst;
    end
end
always @(posedge clk) begin
    if(reset)begin
        abandon <= 1'b0;
    end
    else if(pfs_ex) begin
        abandon <= 1'b0;
    end
    else if( to_fs_valid & ~flush_r & ~flush)begin
        abandon <= 1'b1;
    end
    else if(inst_sram_data_ok ) begin
        abandon <= 1'b0;
    end
    /*else if( ~fs_allowin & ~fs_ready_go & ~flush_r & ~flush)begin
        abandon <= 1'b1;
    end*/
    else begin
        abandon <= abandon;
    end
end

always @(posedge clk) begin
    if(reset|flush|flush_r)begin
        br_bus_r_valid <= 1'b0;
    end
    else if(br_taken & fs_valid & fs_ready_go & ds_allowin & inst_sram_addr_ok)begin
        br_bus_r_valid <= 1'b0;
    end
    else if(pfs_ready_go & fs_allowin & bd_done)begin
        br_bus_r_valid <= 1'b0;
    end
    else if(br_taken) begin
        br_bus_r_valid <= 1'b1;
    end
    
    else begin
        br_bus_r_valid <= br_bus_r_valid;
    end
end

always @(posedge clk) begin
    if(reset)begin
        br_bus_r <= 0;
    end
    else if(br_bus_r_valid & ~fs_bd)begin
        br_bus_r <= br_bus_r ;
    end
    else begin
        br_bus_r <= br_bus;
    end
end

always @(posedge clk) begin
    if(reset)begin
        bd_done <= 1'b0;
    end
    else if(pfs_ready_go & fs_allowin & bd_done& (br_bus_r_valid|br_taken))begin
        bd_done <= 1'b0;
    end
    else if(br_taken & fs_valid & fs_ready_go & ds_allowin & inst_sram_addr_ok)begin
        bd_done <= 1'b0;
    end
    else if((br_taken|br_bus_r_valid) & (fs_valid|to_fs_valid)) begin
        bd_done <= 1'b1;
    end
    else begin
        bd_done <= bd_done;
    end
end
always @(posedge clk) begin
    if(reset)begin
        flush_r <= 1'b0;
    end
    else if(pfs_ready_go & fs_allowin & ~abandon )begin
        flush_r <= 1'b0;
    end
    else if(flush)begin
        flush_r <= 1'b1;
    end
    else begin
        flush_r <= flush_r;
    end
end
always @(posedge clk) begin
    if(reset)begin
        eret_r <= 1'b0;
    end
    else if(pfs_ready_go & fs_allowin & ~abandon)begin
        eret_r <= 1'b0;
    end
    else if(eret)begin
        eret_r <= 1'b1;
    end
    else begin
        eret_r <= eret_r;
    end
end

always @(posedge clk) begin
    if(reset)begin
        epc_r <= 0;
    end
    else if(eret_r)begin
        epc_r <= epc_r ;
    end
    else begin
        epc_r <= epc;
    end
end

always @(posedge clk) begin
    if(reset)begin
        tlb_r_r <= 1'b0;
    end
    else if(pfs_ready_go & fs_allowin & ~abandon)begin
        tlb_r_r <= 1'b0;
    end
    else if(tlb_r)begin
        tlb_r_r <= 1'b1;
    end
    else begin
        tlb_r_r <= tlb_r_r;
    end
end

always @(posedge clk) begin
    if(reset)begin
        fs_refetch_r <= 1'b0;
    end
    else if(fs_ready_go & ds_allowin)begin
        fs_refetch_r <= 1'b0;
    end
    else if(fs_refetch)begin
        fs_refetch_r <= 1'b1;
    end
    else begin
        fs_refetch_r <= fs_refetch_r;
    end
end

always @(posedge clk) begin
    if(reset)begin
        ws_refetch_r <= 1'b0;
    end
    else if(pfs_ready_go & fs_allowin & ~abandon)begin
        ws_refetch_r <= 1'b0;
    end
    else if(ws_refetch)begin
        ws_refetch_r <= 1'b1;
    end
    else begin
        ws_refetch_r <= ws_refetch_r;
    end
end
always @(posedge clk) begin
    if(reset)begin
        ws_pc_r <= 0;
    end
    else if(ws_refetch_r)begin
        ws_pc_r <= ws_pc_r ;
    end
    else begin
        ws_pc_r <= ws_pc;
    end
end

assign inst_sram_req   = /*~inst_sram_data_ok*/ ~reset & fs_allowin & ~br_stall ;
assign inst_sram_wr    = 1'b0;
assign inst_sram_size  = 2'b10;
assign inst_sram_wstrb = 4'h0;
assign inst_sram_addr  = nextpc;
assign inst_sram_wdata = 32'b0;

assign fs_inst         = (fs_ex)? 32'b0:
                        (save)?save_inst:inst_sram_rdata ;

endmodule
